package de.tum.in.eist.poll.server;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;
import de.tum.in.eist.poll.shared.Poll;
import de.tum.in.eist.poll.shared.Result;
import de.tum.in.eist.poll.shared.Statistic;
import de.tum.in.eist.poll.shared.exceptions.DuplicatePollException;
import de.tum.in.eist.poll.shared.exceptions.PollNotFoundException;

public class PollDatabaseManager {

	private final DatastoreService datastore = DatastoreServiceFactory
			.getDatastoreService();

	/**
	 * Returns a list of all the polls in the database
	 * 
	 * 
	 * @return list of all polls
	 */

	public List<Poll> getAllPolls() {
		List<Poll> polls = new ArrayList<Poll>();
		Query pollQuery = new Query("Poll").addSort("Date", SortDirection.ASCENDING);
		for (Entity e : datastore.prepare(pollQuery).asIterable()) {
			Poll poll = asPoll(e);
			polls.add(poll);
		}
		return polls;
	}

	/**
	 * Returns the currently open polls as a list of dates
	 * 
	 * @return list of dates representing the open polls
	 */
	public List<Poll> getOpenPolls() {
		List<Poll> openPolls = new ArrayList<Poll>();
		Date currentDate = new Date();
		Query pollQuery = new Query("Poll");
		pollQuery.addFilter("End", FilterOperator.GREATER_THAN, currentDate);
		pollQuery.addSort("Date", SortDirection.ASCENDING);
		for (Entity e : datastore.prepare(pollQuery).asIterable()) {
			if (currentDate.after((Date) e.getProperty("Start"))) {
				Poll openPoll = asPoll(e);
				openPolls.add(openPoll);
			}
		}
		return openPolls;
	}

	/**
	 * 
	 * Returns a list of all already closed polls
	 * 
	 * 
	 * 
	 * @return List of closed polls
	 */

	public List<Poll> getClosedPolls() {
		List<Poll> closedPolls = new ArrayList<Poll>();
		Date currentDate = new Date();
		Query pollQuery = new Query("Poll");
		pollQuery.addFilter("End", FilterOperator.LESS_THAN, currentDate);
		pollQuery.addSort("Date", SortDirection.ASCENDING);
		for (Entity e : datastore.prepare(pollQuery).asIterable()) {
			Poll closedPoll = asPoll(e);
			closedPolls.add(closedPoll);
		}
		return closedPolls;
	}

	/**
	 * Returns the poll object belonging the specified date
	 * 
	 * @param pollDate
	 *            date identifier of the poll
	 * @return Poll
	 * @throws PollNotFoundException
	 *             if the specified poll does not exist in the database
	 */
	public Poll getPoll(Date pollDate) throws PollNotFoundException {
		Key pollKey = KeyFactory.createKey("Poll", pollDate.toString());
		try {
			return asPoll(datastore.get(pollKey));
		} catch (EntityNotFoundException e) {
			throw new PollNotFoundException();
		}
	}

	/**
	 * 
	 * Returns all the Polls specified in the list of dates
	 * 
	 * @param pollDates
	 *            the date identfiers of the polls
	 * @return a list of polls
	 */

	public List<Poll> getPolls(List<Date> pollDates) {
		List<Poll> polls = new ArrayList<Poll>();
		for (Date pollDate : pollDates) {
			try {
				polls.add(getPoll(pollDate));
			} catch (PollNotFoundException e) {

			}
		}
		return polls;
	}

	/**
	 * Generates a Statistic object containing all the information of the
	 * results from a certain Poll
	 * 
	 * 
	 * @param pollDate
	 *            identifier of the poll
	 * @return Statistics from the poll result data
	 * @throws PollNotFoundException
	 *             if the specified poll does not exist
	 */

	public Statistic getStatistic(Date pollDate) throws PollNotFoundException {
		Key pollKey = KeyFactory.createKey("Poll", pollDate.toString());
		Query resultQuery = new Query("Result", pollKey);
		Statistic stat = new Statistic(pollDate);
		for (Entity result : datastore.prepare(resultQuery).asIterable()) {
			int q1 = (Integer) result.getProperty("Question1");
			String q2 = (String) result.getProperty("Question2");
			String q3 = (String) result.getProperty("Question3");
			stat.addQuestions(q1, q2, q3);
		}
		return stat;

	}

	/**
	 * Adds a new uniquely identified poll into the database
	 * 
	 * @param poll
	 *            the poll object that should be added
	 * @throws DuplicatePollException
	 *             if there is already a poll in the database with the same
	 *             identifier
	 */

	public void addPoll(Poll poll) throws DuplicatePollException {
		try {
			getPoll(poll.getDate());
			throw new DuplicatePollException();
		} catch (PollNotFoundException e) {
			datastore.put(asEntity(poll));
		}
	}

	/**
	 * Adds the feedback of an anonymous student to the database
	 * 
	 * 
	 * @param Result
	 *            the result object containing the answers
	 * 
	 * @throws PollNotFoundException
	 *             if the specified poll does not exist in the database
	 */
	public void addResult(Result r) throws PollNotFoundException {
		Key parentPollKey = KeyFactory.createKey("Poll", r.getDateOfLecture()
				.toString());
		Entity pollResult = new Entity("Result", parentPollKey);
		pollResult.setProperty("Question1", r.getQuestion1());
		pollResult.setProperty("Question2", r.getQuestion2());
		pollResult.setProperty("Question3", r.getQuestion3());

		datastore.put(pollResult);
	}

	/**
	 * 
	 * Updates the specified polls details
	 * 
	 * @param poll
	 *            the updated poll object
	 * 
	 * @throws PollNotFoundException
	 *             if the specified poll does not exist in the database
	 */
	public void updatePoll(Poll poll) throws PollNotFoundException {
		if (!this.contains(poll)) {
			throw new PollNotFoundException();
		}
		datastore.put(asEntity(poll));
	}

	/**
	 * Deletes the specified poll and all its results from the database
	 * 
	 * @param pollDate
	 *            date identifier of the poll
	 * @throws PollNotFoundException
	 *             if the specified poll does not exist in the database
	 */
	public void deletePoll(Date pollDate) {
		Key pollKey = KeyFactory.createKey("Poll", pollDate.toString());
		Query q = new Query("Result", pollKey).setKeysOnly();
		for (Entity result : datastore.prepare(q).asIterable()) {
			datastore.delete(result.getKey());
		}
		datastore.delete(pollKey);
	}

	/**
	 * Checks if the poll exists in the database
	 * 
	 * @param poll
	 *            the poll to check
	 * @return whether the poll exists in the database or not
	 */

	public boolean contains(Poll poll) {
		Key pollKey = KeyFactory.createKey("Poll", poll.getDate().toString());
		try {
			datastore.get(pollKey);
			return true;
		} catch (EntityNotFoundException e) {
			return false;
		}
	}

	/**
	 * Converts an Entity object to a Poll object
	 * 
	 * @param poll
	 *            the Poll to convert
	 * @return the Entity as a Poll
	 * 
	 */

	private static Poll asPoll(Entity entity) {
		return new Poll((String) entity.getProperty("Topic"),
				(Date) entity.getProperty("Date"),
				(Date) entity.getProperty("Start"),
				(Date) entity.getProperty("End"));
	}

	/**
	 * Converts an Poll object to an Entity object
	 * 
	 * @param entity
	 *            the Entity to convert
	 * 
	 * @return the Poll as an Entity
	 * 
	 * 
	 */

	private static Entity asEntity(Poll p) {
		Entity e = new Entity("Poll", p.getDate().toString());
		e.setProperty("Topic", p.getTopic());
		e.setProperty("Date", p.getDate());
		e.setProperty("Start", p.getStart());
		e.setProperty("End", p.getEnd());
		return e;
	}

}
